#include <stdlib.h>
#include <string.h>

#include "onigvw.h"
#include "ptrreg.h"

#define ONIG_VW_VERSION "3.0.0"

struct callback_packet {
	int cnt;
	OnigRegion *region;
	OnigVWNamedGroupNumber *ngn;
};


static int name_callback(
	const UChar *name, 
	const UChar *name_end,
	int ngroup_num, 
	int *group_nums,
	regex_t *reg, 
	struct callback_packet *arg
) {
	int i, len;
	OnigEncoding enc = onig_get_encoding(reg);
	OnigVWNamedGroupNumber *current = &arg->ngn[arg->cnt];

	current->ngroup_num = ngroup_num;
	current->group_num = malloc(sizeof(int) * ngroup_num);

	len = onigenc_str_bytelen_null(enc, name);
	current->name = malloc(len + ONIGENC_MBC_MINLEN(enc));
	memcpy(current->name, name, len);
	for (i = 0; i < ONIGENC_MBC_MINLEN(enc); i++)
	{
		current->name[len + i] = '\0';
	}

	for (i = 0; i < ngroup_num; i++) 
	{	
		current->group_num[i] = group_nums[i];
	}
	arg->cnt++;
	return 0;  
}


static OnigEncodingType*
int2encoding(int index) 
{
	switch( index ) {
		case 0: return ONIG_ENCODING_ASCII;
		case 1: return ONIG_ENCODING_ISO_8859_1;
		case 2: return ONIG_ENCODING_ISO_8859_2;
		case 3: return ONIG_ENCODING_ISO_8859_3;
		case 4: return ONIG_ENCODING_ISO_8859_4;
		case 5: return ONIG_ENCODING_ISO_8859_5;
		case 6: return ONIG_ENCODING_ISO_8859_6;
		case 7: return ONIG_ENCODING_ISO_8859_7;
		case 8: return ONIG_ENCODING_ISO_8859_8;
		case 9: return ONIG_ENCODING_ISO_8859_9;
		case 10: return ONIG_ENCODING_ISO_8859_10;
		case 11: return ONIG_ENCODING_ISO_8859_11;
		case 12: return ONIG_ENCODING_ISO_8859_11;
		case 13: return ONIG_ENCODING_ISO_8859_13;
		case 14: return ONIG_ENCODING_ISO_8859_14;
		case 15: return ONIG_ENCODING_ISO_8859_15;
		case 16: return ONIG_ENCODING_ISO_8859_16;
		case 17: return ONIG_ENCODING_UTF8;
		case 18: return ONIG_ENCODING_UTF16_BE;
		case 19: return ONIG_ENCODING_UTF16_LE;
		case 20: return ONIG_ENCODING_UTF32_BE;
		case 21: return ONIG_ENCODING_UTF32_LE;
		case 22: return ONIG_ENCODING_EUC_JP;
		case 23: return ONIG_ENCODING_EUC_TW;
		case 24: return ONIG_ENCODING_EUC_KR;
		case 25: return ONIG_ENCODING_EUC_CN;
		case 26: return ONIG_ENCODING_SJIS;
		/*case 27: return ONIG_ENCODING_KOI8;*/
		case 28: return ONIG_ENCODING_KOI8_R;
		case 29: return ONIG_ENCODING_CP1251;
		case 30: return ONIG_ENCODING_BIG5;
		case 31: return ONIG_ENCODING_GB18030;
		case 32: return ONIG_ENCODING_UNDEF;
	}
	return ONIG_ENCODING_UNDEF;
}


static OnigSyntaxType* 
int2syntax(int index) 
{
	switch( index ) {
		case 0: return ONIG_SYNTAX_ASIS;
		case 1: return ONIG_SYNTAX_POSIX_BASIC;
		case 2: return ONIG_SYNTAX_POSIX_EXTENDED;
		case 3: return ONIG_SYNTAX_EMACS;
		case 4: return ONIG_SYNTAX_GREP;
		case 5: return ONIG_SYNTAX_GNU_REGEX;
		case 6: return ONIG_SYNTAX_JAVA;
		case 7: return ONIG_SYNTAX_PERL;
		case 8: return ONIG_SYNTAX_PERL_NG;
		case 9: return ONIG_SYNTAX_RUBY;
		case 10: return ONIG_SYNTAX_DEFAULT;
    }
    return ONIG_SYNTAX_DEFAULT;
}


extern regex_t* 
onig_vw_new(const OnigUChar *pattern, OnigOptionType option, int encIndex, int syntaxIndex, OnigUChar *errorMessage)
{
	regex_t *reg;
	OnigErrorInfo einfo;
	OnigEncoding enc = int2encoding(encIndex);
	OnigSyntaxType *syntax = int2syntax(syntaxIndex);
	int result = onig_new (
					&reg,
					pattern,
					pattern + onigenc_str_bytelen_null(enc, pattern),
					option,
					enc,
					syntax,
					&einfo);

	if (result == ONIG_NORMAL)
	{
		errorMessage[0] = '\0';
	} else {
        char estr[ONIG_MAX_ERROR_MESSAGE_LEN];
        onig_error_code_to_str(estr, result, &einfo);
		memcpy(errorMessage, estr, ONIG_MAX_ERROR_MESSAGE_LEN);
	}

	return reg;
}


extern int 
onig_vw_search(regex_t *reg, const OnigUChar *str, int startPos, OnigRegion *region)
{
	OnigEncoding enc = onig_get_encoding(reg);
	OnigOptionType option = onig_get_options(reg);
	const OnigUChar *end = str + onigenc_str_bytelen_null(enc, str);
	const OnigUChar *start = str + (sizeof(OnigUChar) * startPos);

	return onig_search (
					reg,
					str,
					end,
					start,
					end,
					region,
					option);
}


extern OnigVWNamedGroupNumber*
onig_vw_named_group_numbers(regex_t *reg, OnigRegion *region)
{
	struct callback_packet packet;
	OnigVWNamedGroupNumber *ngn = malloc(sizeof(OnigVWNamedGroupNumber) * onig_number_of_names(reg));
	packet.cnt = 0;
	packet.ngn = ngn;
	packet.region = region;
	onig_foreach_name(reg, (void *)name_callback, &packet);
	return ngn;
}


extern int 
onig_vw_enc_mbc_minlen(int encIndex)
{
	OnigEncoding enc = int2encoding(encIndex);
	return ONIGENC_MBC_MINLEN(enc);
}


extern void
onig_vw_named_group_number_free(OnigVWNamedGroupNumber *ngn)
{
	int i, len;
	len = sizeof(ngn) / sizeof(OnigVWNamedGroupNumber);
	for (i = 0; i < len; i++)
	{
		free(ngn[i].group_num);
		free(ngn[i].name);
	}
	free(ngn);
}


extern OnigRegion* 
onig_vw_region_new(void)
{
	return onig_region_new();
}


extern void 
onig_vw_free(regex_t *reg)
{
	if (reg == NULL) { return; }
	onig_free(reg);
}


extern void 
onig_vw_region_free(OnigRegion *region, int free_self)
{
	onig_region_free(region, free_self);
}


extern int 
onig_vw_number_of_names(regex_t *reg) 
{
	return onig_number_of_names(reg);
}


extern int
onig_vw_number_of_captures(regex_t *reg)
{
	return onig_number_of_captures(reg);
}


extern const char*
onig_vw_version(void)
{
	return ONIG_VW_VERSION;
}


extern const char* 
oniguruma_version(void) 
{
	return onig_version();
}


extern unsigned long 
onig_vw_register(regex_t *reg)
{
	return ptrreg_set(reg);
}


extern regex_t*
onig_vw_unregister(unsigned long key)
{
	return ptrreg_remove(key);
}


extern void
onig_vw_release(unsigned long key)
{
	regex_t *reg = onig_vw_unregister(key);
	onig_vw_free(reg);
}


extern unsigned long 
onig_vw_id_limit(void)
{
	return ptrreg_key_limit();
}


extern void 
onig_vw_reset_id(void)
{
	ptrreg_reset_key();
}

